from typing import Generic, TypeVar, Type, List

from fastapi.encoders import jsonable_encoder
from pydantic import BaseModel
from sqlalchemy import delete, select, update, desc, asc
from sqlalchemy.ext.asyncio import AsyncSession

from commons.eo_vo import DO_PRIMARY_KEY, CBase, PageQueryModel
from module_admin.entity.vo.common_vo import CrudResponseModel
from utils.common_util import CamelCaseUtil, export_list2excel
from utils.page_util import PageUtil

EO = TypeVar("EO", bound=CBase)
VO = TypeVar("VO", bound=BaseModel)
QPVO = TypeVar("QPVO", bound=PageQueryModel)


def obj_dump_without_page(o):
    return jsonable_encoder(o, exclude={'page_num', 'page_size'}, exclude_unset=True, exclude_none=True, by_alias=False)


class CRUDDao(Generic[EO, VO, QPVO]):
    mo: Type[EO]

    @classmethod
    def pk_name(cls):
        return DO_PRIMARY_KEY[cls.mo.__name__]

    @classmethod
    async def get_detail_by_id(cls, db: AsyncSession, pk_id: int):
        """
        根据id获取详细信息

        :param db: orm对象
        :param pk_id: id
        :return: 对象
        """
        q = {DO_PRIMARY_KEY[cls.mo.__name__]: pk_id}
        return (await db.execute(select(cls.mo).filter_by(**q))).scalars().first()

    @classmethod
    async def get_detail_by_vo(cls, db: AsyncSession, vo: VO):
        """
        根据查询对象获取对象

        :param db: orm对象
        :param vo: 对象
        :return: 对象
        """
        q = obj_dump_without_page(vo)
        return (await db.execute(select(cls.mo).filter_by(**q))).scalars().first()

    @classmethod
    async def get_list(cls, db: AsyncSession, query_object: QPVO, is_page: bool = False):
        """
        根据查询对象获取对象列表信息

        :param db: orm对象
        :param query_object: 查询参数对象
        :param is_page: 是否开启分页
        :return: 对象列表信息
        """
        q = obj_dump_without_page(query_object)
        criterion, sorting = [], []
        for k, v in q.items():
            if k == 'sort':
                if not v: continue
                for s in v.split(','):
                    rs = s if s[0] in ['-', '+'] else ('+' + s)
                    if rs[0] == '-':
                        sorting.append(desc(getattr(cls.mo, rs[1:], None)))
                    else:
                        sorting.append(asc(getattr(cls.mo, rs[1:], None)))
            if k.startswith('begin_'):
                f = getattr(cls.mo, k.replace('begin_', ''), None)
                if f: criterion.append(f >= v)
            elif k.startswith('end_'):
                f = getattr(cls.mo, k.replace('end_', ''), None)
                if f: criterion.append(f < v)
            else:
                f = getattr(cls.mo, k, None)
                if f: criterion.append(f == v)

        query = select(cls.mo).filter(*criterion).order_by(*sorting).distinct() if sorting else select(cls.mo).filter(*criterion).distinct()
        return await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page)

    @classmethod
    async def add(cls, db: AsyncSession, vo: VO):
        """
        新增对象

        :param db: orm对象
        :param vo: 对象
        :return:
        """
        data = cls.mo(**vo.model_dump(include=cls.mo.model_fields_set()))
        db.add(data)
        await db.flush()

        return data

    @classmethod
    async def edit(cls, db: AsyncSession, vo: VO):
        """
        编辑对象

        :param db: orm对象
        :param vo: 需要更新的对象
        :return:
        """
        # q = jsonable_encoder(vo, include={DO_PRIMARY_KEY[cls.mo.__name__]}, exclude_unset=True, exclude_none=True, by_alias=False)
        # await db.select(cls.mo).filter_by(**q).update(vo.model_dump(include=cls.mo.model_fields_set()))
        await db.execute(update(cls.mo), [vo.model_dump(include=cls.mo.model_fields_set())])

    @classmethod
    async def delete(cls, db: AsyncSession, vo: VO):
        """
        删除对象

        :param db: orm对象
        :param vo: 对象
        :return:
        """
        q = jsonable_encoder(vo, include={DO_PRIMARY_KEY[cls.mo.__name__]}, exclude_unset=True, exclude_none=True,
                             by_alias=False)
        await db.execute(delete(cls.mo).filter_by(**q))


class CRUDService(Generic[EO, VO, QPVO]):
    dao: Type[CRUDDao]
    vo: Type[VO]
    mapping_dict: dict = {}
    msg: str = ''

    @classmethod
    async def get_list(cls, db: AsyncSession, query_object: QPVO, is_page: bool = False):
        return await cls.dao.get_list(db, query_object, is_page)

    @classmethod
    async def detail(cls, db: AsyncSession, id: int) -> VO:
        data = await cls.dao.get_detail_by_id(db, id)
        return cls.vo(**CamelCaseUtil.transform_result(data)) if data else None

    @classmethod
    async def add(cls, db: AsyncSession, vo: VO):
        eo_db = await cls._detail_by_vo(db, vo)
        if eo_db:
            result = dict(is_success=False, message=f'{cls.msg}已存在')
        else:
            try:
                await cls.dao.add(db, vo)
                await cls._post_edit(db, vo)
                await db.commit()
                result = dict(is_success=True, message=f'{cls.msg}新增成功')
            except Exception as e:
                await db.rollback()
                raise e

        return CrudResponseModel(**result)

    @classmethod
    async def edit(cls, db: AsyncSession, vo: VO):
        vo_dict = vo.model_dump(exclude_unset=True)
        vo_detail = await cls.detail(db, vo_dict.get(cls.dao.pk_name()))
        if vo_detail:
            if await cls._check_edit_dup(db, vo, vo_detail):
                result = dict(is_success=False, message=f'{cls.msg}已存在')
                return CrudResponseModel(**result)
            try:
                await cls._pre_edit(db, vo, vo_detail)
                await cls.dao.edit(db, vo)
                await cls._post_edit(db, vo)
                await db.commit()
                result = dict(is_success=True, message=f'{cls.msg}更新成功')
            except Exception as e:
                await db.rollback()
                raise e
        else:
            result = dict(is_success=False, message=f'{cls.msg}不存在')

        return CrudResponseModel(**result)

    @classmethod
    async def delete(cls, db: AsyncSession, ids: str):
        id_lst = ids.strip().strip(',').split(',')
        if id_lst:
            try:
                for id in id_lst:
                    await cls.dao.delete(db, {cls.dao.pk_name(): id})
                await cls._post_edit(db, None)
                await db.commit()
                result = dict(is_success=True, message=f'{cls.msg}删除成功')
            except Exception as e:
                await db.rollback()
                raise e
        else:
            result = dict(is_success=False, message='传入id为空')
        return CrudResponseModel(**result)

    @classmethod
    async def _post_edit(cls, db: AsyncSession, vo: VO | None):
        """
        增、删、改 之后都会触发
        :param db:
        :param vo:
        :return:
        """
        pass

    @classmethod
    async def _pre_edit(cls, db: AsyncSession, vo: VO, db_vo: VO):
        """
        添加前的数据预处理
        :param db:
        :param vo:
        :param db_vo:
        :return:
        """
        pass

    @classmethod
    async def _check_edit_dup(cls, db: AsyncSession, vo: VO, db_vo: VO):
        """
        编辑保存前检查是否重复
        :param db:
        :param vo:
        :param db_vo:
        :return:
        """
        return False

    @classmethod
    async def _detail_by_vo(cls, db: AsyncSession, vo: VO) -> VO | None:
        """
        新增前查重
        :param db:
        :param vo:
        :return:
        """
        return await cls.dao.get_detail_by_vo(db, vo)

    @classmethod
    def _clean_data(cls, data: List):
        """
        导出前清洗数据（比如字典翻译）
        :param data:
        :return:
        """
        return data

    @classmethod
    def export(cls, data: List):
        data = cls._clean_data(data)
        new_data = [{cls.mapping_dict.get(key): value for key, value in item.items() if cls.mapping_dict.get(key)}
                    for item in data]
        binary_data = export_list2excel(new_data)
        return binary_data
